package xj.graph2d;

import java.util.Stack;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class UndoManager {

  /**
   * Add a command to the undo stack without executing the command
   */
  public void addCommand(Command command, boolean execute) {
    checkStack();
    if (command != null) {
      if (execute) {
	command.execute();
      }

      undoStack.push(command);
      redoStack.clear();
      postChangeEvent();

      if (debug) {
	System.out.println("UndoManager.addCommand() execute=" + execute);
	dump();
      }
    }
  }

  public void addCommand(Command command) {
    addCommand(command, false);
  }

  public void executeCommand(Command command) {
    addCommand(command, true);
  }

  public void undoCommand(boolean revoke) {
    checkStack();
    if (!undoStack.empty()) {
      Command command = undoStack.peek(); // looking at the top element without popping it
      undoStack.pop();
      command.undo();
      if (!revoke) {
	redoStack.push(command);
      }
      postChangeEvent();

      if (debug) {
	System.out.println("UndoManager.undoCommand()");
	dump();
      }
    }
  }

  public void undoCommand() {
    undoCommand(false);
  }

  public void redoCommand() {
    if (!redoStack.empty()) {
      Command command = redoStack.peek(); // looking at the top element
      redoStack.pop();
      command.execute();
      undoStack.push(command);
      postChangeEvent();
      
      if (debug) {
	System.out.println("UndoManager.redoCommand()");
	dump();
      }
    }
  }

  public void revokeLastCommand() {
    undoCommand(true);
  }

  public boolean hasUndoCommand() {
    return !undoStack.isEmpty();
  }

  public boolean hasRedoCommand() {
    return !redoStack.isEmpty();
  }

  public void save() {
    savePos = undoStack.size();
  }

  public boolean isChanged() {
    return (savePos != undoStack.size());
  }

  public void setChangeListener(ChangeListener listener) {
    this.listener = listener;
  }

  public void reset() {
    undoStack.clear();
    redoStack.clear();
    savePos = 0;
  }

  public void postChangeEvent() {
    if (listener != null) {
      if (event == null) {
	event = new ChangeEvent(this);
      }
      listener.stateChanged(event);
    }
  }

  protected void checkStack() {
    if (!undoStack.empty()) {
      // remove insignificant commands
      Command lastCmd = undoStack.peek();
      if (!lastCmd.isSignificant()) {
	undoStack.pop();
      }
    }
  }

  public void dump() {
    System.out.println("== Begin UndoManager state:");
    System.out.println("-- undoStack");
    int i;
    for (i = 0; i < undoStack.size(); i++) {
      System.out.println("  [" + i + "] " + undoStack.get(i));
    }
    System.out.println("-- redoStack");
    for (i = 0; i < redoStack.size(); i++) {
      System.out.println("  [" + i + "] " + redoStack.get(i));
    }
    System.out.println("== End UndoManager state:");
  }

  protected Stack<Command> undoStack = new Stack<Command>();

  protected Stack<Command> redoStack = new Stack<Command>();

  protected int savePos = 0;

  protected ChangeListener listener;

  protected ChangeEvent event;

  protected static final boolean debug = false;

}
